/**
 * 
 */
package com.devmindset.legoframework.client.mvc;

import com.allen_sauer.gwt.log.client.Log;
import com.devmindset.legoframework.client.mvc.metadata.ComponentStory;
import com.devmindset.legoframework.client.mvc.metadata.HasStory;
import com.devmindset.legoframework.client.mvc.util.ErrorDialog;
import com.devmindset.legoframework.client.screen.ApplicationScreenManager;
import com.smartgwt.client.widgets.Canvas;



/**
 * Base class for all the components (the GUI building blocks) in the
 * application.<br/><br/>
 * 
 * Each component is handled by a controller that sends the events along with
 * the prepared model to this controller. The component can have 2 states:
 * pre-construct (construct() method is called) and initialized (calculate()
 * method is called).<br/><br/>
 * 
 * This is a 'dumb' component: it just
 * knows to render and calculate itself based on a prepared data by the
 * controller.<br/><br/> 
 * 
 * Correspondingly, it can't invoke services via RPC to retrieve new
 * data, all the data is prepared by it's controller.<br/><br/> 
 * 
 * In addition, this component can signal other events using the
 * {@link LegoComponent#broadcastEvent(LegoEvent)} method.<br/><br/>
 * 
 * @author <a href="mailto:ice.penov@devmindset.com">Ice Penov</a>
 */
public abstract class LegoComponent implements HasStory<ComponentStory> {

  private Canvas result;  
  private boolean initialized;
  

  /**
   * Returns the visual output result of this {@link LegoComponent} as
   * {@link Canvas}, which is either the result of {@link LegoComponent#construct(LegoModel)} 
   * or {@link LegoComponent#recalculate(LegoModel)}.
   * 
   * @return the visual output of this {@link LegoComponent}
   */
  public final Canvas getResult() {
    return result;
  }

  /**
   * Sets the visual output result of this {@link LegoComponent} as
   * {@link Canvas}, which is either the result of
   * {@link LegoComponent#construct(LegoModel)} or
   * {@link LegoComponent#calculate(LegoModel)}.
   * 
   * @param result the visual output of this {@link LegoComponent}
   */
  public final void setResult(Canvas result) {
    this.result = result;
  }

  /**
   * Place to put your construction and initialization code for this
   * {@link LegoComponent}. 
   * 
   * This is done only once per application instance to prevent unnecessary
   * component re-construction.
   * 
   * @param model the model used to construct this {@link LegoComponent}
   * 
   * @return the visual output of this {@link LegoComponent}
   */
  public abstract Canvas construct(LegoModel model);

  /**
   * After the component has been constructed, this method is called to 'dress-up' that
   * naked constructed component with the passed {@link LegoModel}. 
   * 
   * Place this kind of calculation code here for this {@link LegoComponent} after this
   * component has been constructed using the {@link LegoComponent#construct()} method.
   * 
   * @param model the model used to calculate this {@link LegoComponent}
   */
  public abstract void calculate(LegoModel model);

  /**
   * Renders this {@link LegoComponent} given it's {@link LegoModel}.
   * 
   * Rendering means: either {@link LegoComponent#construct(LegoModel)} or
   * {@link LegoComponent#calculate(LegoModel)} will be called depending on
   * the component state.
   *  
   * @param model the model used to render this {@link LegoComponent}
   */
  public final void render(LegoModel model) {
    try {
      if (!initialized) {
        setResult(construct(model));
        Log.debug("Component not initialized - constructing [ " + this.myStory().getKnownAs() + " ]");
        initialized = true;
        calculate(model);
        ApplicationScreenManager.placePreparedComponent(this, getResult());
        ApplicationScreenManager.signalCompletionToActiveScreen(this);        
      } else {
        Log.debug("Component already constructed - recalculating [ " + this.myStory().getKnownAs() + " ]");
        calculate(model);
        //place if component not placed on screen
        if (!ApplicationScreenManager.alreadyOnPlaceholder(this)) {
        	Log.debug("Recalculated already constructed component [ " + this.myStory() + " ]. Placing to screen");
          ApplicationScreenManager.placePreparedComponent(this, getResult());        	
        }
      }
    } catch (Throwable e) {
      ErrorDialog.logAndShow(e);
    }
  }
      
	/**
   * This method is the only way of this component to affect the outside world,
   * by signaling an event that other {@link EventMonitor} /
   * {@link LegoComponent} pair should handle.
   * 
   * @param event
   *          the event to be broadcasted
   */
  public void broadcastEvent(LegoEvent event) {
    Log.debug("Broadcasted event type [ " + event.getType() + " ] from source [ " 
    		+ this.myStory() + " ]");
    EventMonitorManager.dispathEvent(event);
  }
  
  /**
   * Returns the state of the {@link LegoComponent}.
   * 
   * @return true if it is already initialized, false otherwise
   */
  public boolean isInitialized() {
    return initialized;
  }
  
  public void clearInitializedState() {
	  this.initialized = false;
  }
  
  public void setInitialized(boolean b) {
	  this.initialized = b;
  }
  
  public void destroy() {
  	;	//does nothing by default.  
  }
    
}